#include "EffectTemplatePCH.h"
#include "EffectManager.h"
#include "Technique.h"
#include "EffectParameter.h"
#include "Effect.h"

Effect Effect::InvalidEffect( NULL, "" );

Effect::Effect( CGcontext context, const std::string& effectFile, const std::string& effectName )
: m_cgContext(context)
, m_cgEffect(NULL)
, m_bIsValid(false)
, m_EffectName( effectName )
, m_TimeStamp(0)
{
    if ( context != NULL && !effectFile.empty() )
    {
        LoadEffectFromFile( effectFile );
    }
}

Effect::~Effect()
{
    Destroy();
}

bool Effect::LoadEffectFromFile( const std::string& effectFile )
{
    bool bSuccess = false;

    CGeffect effect = cgCreateEffectFromFile( m_cgContext, effectFile.c_str(), NULL );

    if ( effect != NULL )
    {
        Destroy();  // Destroy the current effect as we will be loading a new one.

        // Replace the existing effect.
        m_cgEffect = effect;

        // Get the parameters
        CGparameter parameter = cgGetFirstEffectParameter( m_cgEffect );
        while ( parameter != NULL )
        {
            EffectParameter* pParameter = new EffectParameter( m_cgContext, parameter );
            m_ParametersByName.insert( ParameterMap::value_type(pParameter->GetParameterName(), pParameter) );
            if ( pParameter->HasSemantic() )
            {
                m_ParametersBySemantic.insert( ParameterMap::value_type(pParameter->GetParameterSemantic(), pParameter) );
            }

            parameter = cgGetNextParameter( parameter );
        }

        // Get the techniques
        CGtechnique technique = cgGetFirstTechnique( m_cgEffect );
        while ( technique != NULL )
        {
            Technique* pTechnique = new Technique( m_cgContext, technique );
            m_Techniques.push_back(pTechnique);

            // Add the technique to our name map for fast lookup
            m_TechniquesByName.insert( TechniqueMap::value_type(pTechnique->GetTechniqueName(), pTechnique) );

            technique = cgGetNextTechnique(technique);
        }

        m_bIsValid = true;

        EffectLoadedEventArgs loadedArgs( *this, *this );
        OnEffectLoaded( loadedArgs );

        bSuccess = true;
    }

    // Store the filename so we can reload it later if necessary
    m_EffectFile = effectFile;

    // Store the current timestamp
    // Even if the effect fails to load because of a syntax error for example,
    // we should set the current timestamp so we don't continually try to reload
    // the broken effect until the file has been updated and saved.
    m_TimeStamp = time(NULL);

    return bSuccess;
}

void Effect::Destroy()
{
    // Delete the techniques
    TechniqueList::iterator techIter = m_Techniques.begin();
    while ( techIter != m_Techniques.end() )
    {
        delete (*techIter);
        ++techIter;
    }
    m_Techniques.clear();
    m_TechniquesByName.clear();

    // Delete parameters
    ParameterMap::iterator paramIter = m_ParametersByName.begin();
    while ( paramIter != m_ParametersByName.end() )
    {
        delete ( paramIter->second );
        ++paramIter;
    }
    m_ParametersByName.clear();
    m_ParametersBySemantic.clear();

    if ( m_cgEffect != NULL )
    {
        cgDestroyEffect(m_cgEffect);
        m_cgEffect = NULL;
    }

    // Reset the timestamp
    m_TimeStamp = 0;
    m_EffectFile.erase();
}

EffectParameter& Effect::GetParameterByName( const std::string& parameterName )
{
    ParameterMap::iterator parmIter = m_ParametersByName.find( parameterName );
    if ( parmIter != m_ParametersByName.end() )
    {
        return *(parmIter->second);
    }

    return EffectParameter::InvalidEffectParameter;
}
Technique& Effect::GetFirstValidTechnique()
{
    TechniqueList::iterator iter = m_Techniques.begin();
    while ( iter != m_Techniques.end() )
    {
        if ( (*iter)->IsValid() )
        {
            return *(*iter);
        }
    }

    // No valid techniques found.  Return an invalid one.
    return Technique::InvalidTechnique;
}

Technique& Effect::GetTecniqueByName( const std::string& techniqueName )
{
    TechniqueMap::iterator iter = m_TechniquesByName.find( techniqueName );
    if ( iter != m_TechniquesByName.end() )
    {
        return *(iter->second);
    }

    return Technique::InvalidTechnique;
}

Effect::TechniqueList& Effect::GetTechniques()
{
    return m_Techniques;
}

const std::string& Effect::GetEffectName() const
{
    return m_EffectName;
}

time_t Effect::GetTimeStamp() const
{
    return m_TimeStamp;
}

void Effect::CheckForReload()
{
    fs::path filePath = m_EffectFile;
    if ( fs::exists(filePath) && fs::is_regular_file(filePath) && fs::last_write_time(filePath) > m_TimeStamp )
    {
        LoadEffectFromFile( std::string(m_EffectFile) );
    }
}

void Effect::OnEffectLoaded( EffectLoadedEventArgs& e )
{
    EffectLoaded( e );
}